#include "StdAfx.h"
#include "DoubleDragonMachine.h"
#include "Drawtile.h"

void CDoubleDragonMachine::WritePalette(u32 addr,u8 data)
{
/*
palette is 12 bits each entry, first 8 bits are
stored $1000-$11FF, second nibble is stored at
$1200-$13FF.  there is 512 available colors.
it is in the format 

FEDC BA98 7654 3210 - bits
-------------------
XXXX-BBBB GGGG-RRRR - palette format

X = Unused
B = Blue
G = Green
R = Red
*/
u8 r,g,b;

PaletteRAM[addr & 0x3FF] = data;
addr &= 0x1FF;
r = (PaletteRAM[addr | 0x000] & 0x0F) << 4;
g = (PaletteRAM[addr | 0x000] & 0xF0) << 0;
b = (PaletteRAM[addr | 0x200] & 0x0F) << 4;
Palette[addr] = (r << 16) | (g << 8) | (b << 0);
}

void CDoubleDragonMachine::msm5205irq(void *user,int n)
{
CDoubleDragonMachine *M = (CDoubleDragonMachine*)user;
static int DataADPCM[2] = { -1, -1 };

if(M->PosADPCM[n] >= M->EndADPCM[n] || M->PosADPCM[n] >= 0x10000)
	{
	M->IdleADPCM[n] = 1;
	MSM5205_reset_w(n,1);
	}
else if(DataADPCM[n] != -1)
	{
	MSM5205_data_w(n,DataADPCM[n] & 0x0F);
	DataADPCM[n] = -1;
	}
else
	{
	unsigned char *ROM = M->SoundDataROM + 0x10000 * n;

	DataADPCM[n] = ROM[M->PosADPCM[n]++];
	MSM5205_data_w(n,DataADPCM[n] >> 4);
	}
printf("msm5205irq\n");
}

struct MSM5205interface msm5205interface =
	{
	2,384000,
		{
		CDoubleDragonMachine::msm5205irq,
		CDoubleDragonMachine::msm5205irq,
		CDoubleDragonMachine::msm5205irq,
		CDoubleDragonMachine::msm5205irq
		},
	{96,48,64,0},
	{100,100,100,100}
	};

CDoubleDragonMachine::CDoubleDragonMachine()
{
CPU[0] = new CDead6309();
CPU[1] = new CDead63701();
CPU[2] = new CDead6309();
MainCPU = (CDead6309*)CPU[0];
SubCPU = (CDead63701*)CPU[1];
SoundCPU = (CDead6309*)CPU[2];
ym2151 = YM2151Init(0,3579545,44100,this);
MSM5205_sh_start(&msm5205interface);
AddLoadInfo(ROM_CPU1,MainROM);
AddLoadInfo(ROM_CPU2,SubROM);
AddLoadInfo(ROM_CPU3,SoundROM);
AddLoadInfo(ROM_VROM1,CharROM);
AddLoadInfo(ROM_VROM2,SpriteROM);
AddLoadInfo(ROM_VROM3,TileROM);
AddLoadInfo(ROM_SND1,SoundDataROM);
AddLoadInfo(ROM_PROM,PROM);
}

CDoubleDragonMachine::~CDoubleDragonMachine()
{
YM2151Shutdown(ym2151);
}

int CDoubleDragonMachine::Init()
{
Sound->SetUser(ym2151);
YM2151SetIrqHandler(ym2151,YM2151IRQ);
MainCPU->Init();
MainCPU->SetUser(this);
MainCPU->SetOp(ReadMainCPU);
MainCPU->SetRead(ReadMainCPU);
MainCPU->SetWrite(WriteMainCPU);
SubCPU->Init();
SubCPU->SetUser(this);
SubCPU->SetOp(ReadSubCPU);
SubCPU->SetRead(ReadSubCPU);
SubCPU->SetWrite(WriteSubCPU);
SoundCPU->Init();
SoundCPU->SetUser(this);
SoundCPU->SetOp(ReadSoundCPU);
SoundCPU->SetRead(ReadSoundCPU);
SoundCPU->SetWrite(WriteSoundCPU);
return(0);
}

void CDoubleDragonMachine::Kill()
{
YM2151Shutdown(ym2151);
Sound->SetCallback(0);
}

void CDoubleDragonMachine::Reset()
{
int i;

LoadROMs();
for(i=0;i<0x400;i++)
	{
	PaletteRAM[i] = 0;
	WritePalette(i,0);
	}
ProcessInput();
Scanline = 0;
Status = 0x10;
SoundLatch = 0;
BankReg = 0;
ROMPtr = MainROM + 0x10000;
MainCPU->Reset();
SubCPU->Reset();
SoundCPU->Reset();
Sound->SetCallback(ProcessSound);
YM2151ResetChip(ym2151);
}

void CDoubleDragonMachine::Blit()
{
Video->StartBlit();
RenderScreen((u8*)Video->GetSurface(),Video->GetPitch()/4);
Video->StopBlit();
}

#define MAIN_CPU_CLOCK	(3579545 / 60 / 272)

void CDoubleDragonMachine::Line(u32 mainc,u32 soundc,int irq)
{
MainCPU->Execute(MAIN_CPU_CLOCK);
SubCPU->Execute(MAIN_CPU_CLOCK / 3);
SoundCPU->Execute(MAIN_CPU_CLOCK);
Scanline++;
if((Scanline & 0xF) == 0xF)
	MainCPU->FIRQ();
if(Scanline == 240)
	{
	Status |= 8;
	MainCPU->NMI();
	}
if(Scanline == 256)
	Blit();
if(Scanline == 272)
	{
	ProcessInput();
	Scanline = 0;
	Status &= ~8;
	}
}

void CDoubleDragonMachine::Frame()
{
int i;

for(i=0;i<272;i++)
	Line(0,0,0);
}

void CDoubleDragonMachine::SaveState(CState *s)
{
MainCPU->SaveState(s);
SubCPU->SaveState(s);
SoundCPU->SaveState(s);

//ram
s->Write(RAM,0x3800);
s->Write(SubRAM,0x1000);
s->Write(SoundRAM,0x1000);
s->Write(VRAM,0x9800);
s->Write(NVRAM,0x400);
s->Write((u8*)Palette,0x200*4);

//input
s->Write8(Input1);
s->Write8(Input2);
s->Write8(Input3);
s->Write8(Dip1);
s->Write8(Dip2);

//machine
s->Write8(Status);
s->Write32(Scanline);
s->Write8(YM2151Register);
s->Write8(SoundLatch);
s->Write32(ScrollX);
s->Write32(ScrollY);
s->Write8(BankReg);
}

void CDoubleDragonMachine::LoadState(CState *s)
{
MainCPU->LoadState(s);
SubCPU->LoadState(s);
SoundCPU->LoadState(s);

//ram
s->Read(RAM,0x3800);
s->Read(SubRAM,0x1000);
s->Read(SoundRAM,0x1000);
s->Read(VRAM,0x9800);
s->Read(NVRAM,0x400);
s->Read((u8*)Palette,0x200*4);

//input
Input1 = s->Read8();
Input2 = s->Read8();
Input3 = s->Read8();
Dip1 = s->Read8();
Dip2 = s->Read8();

//machine
Status = s->Read8();
Scanline = s->Read32();
YM2151Register = s->Read8();
SoundLatch = s->Read8();
ScrollX = s->Read32();
ScrollY = s->Read32();
BankReg = s->Read8();
ROMPtr = MainROM + 0x10000 + ((BankReg & 0xE0) >> 5) * 0x4000;
}

typedef struct SpriteStruct_s
	{
	u8 y;
	u8 flags;
	u16 tile;
	u8 x;
	} SpriteStruct;

#define TILEMAP(xx,yy)	((xx & 0xF) + ((yy & 0xF) << 4) + ((xx & 0x10) << 4) + ((yy & 0x10) << 5))

void CDoubleDragonMachine::RenderScreen(u8 *ddest,int pitch)
{
u32 *dest = (u32*)ddest;
u32 *d;
int i,x,y,sx,sy;
u16 tile;
u8 attrib;
SpriteStruct *spr;

memset32(dest,Palette[0],256*256);
//render bottom layer (working...)
for(y=1;y<15;y++)				//16 tiles tall
	{
	for(x=0;x<16;x++)			//16 tiles wide
		{
		sx = x + (ScrollX / 16);
		sy = y + (ScrollY / 16);
		i = (TILEMAP(sx,sy) & 0x3FF) << 1;
		tile = BGRAM[i+1] | (BGRAM[i+0] & 7) << 8;
		attrib = (BGRAM[i+0] & 0x38) << 1;
		d = dest + (15 - (ScrollX & 0xF));
		d += (ScrollY & 0xF) * pitch;
		drawtile_16x16_ddragon(d,pitch,tile,x * 16,y * 16,attrib,TileROM,Palette+256);
		}
	}

//render sprites (broken)
/*if(RomSet->GetFlags() & F_CHINAGATE)
	spr = (SpriteStruct*)ChinaGateSpriteRAM;
else*/
	spr = (SpriteStruct*)(SPRRAM + 0x800);
for(i=0;i<64;i++,spr++)
	{
	if((spr->flags & 0x80) == 0)		//is sprite visible?
		continue;
	x = 240 - spr->x + ((spr->flags & 2) << 7);
	y = 240 - spr->y + ((spr->flags & 1) << 8);
	//todo: account for ddragon2 and china gate
	attrib = (spr->tile >> 12) & 7;
	tile = spr->tile & 0xFFF;
	tile &= ~((spr->flags >> 4) & 3);
	drawtile_16x16_ddragon_spr(dest,pitch,tile,x,y,attrib,SpriteROM,Palette+128);
	if(spr->flags & 0x10)
		drawtile_16x16_ddragon_spr(dest,pitch,tile+1,x,y-16,attrib,SpriteROM,Palette+128);
	if(spr->flags & 0x20)
		drawtile_16x16_ddragon_spr(dest,pitch,tile+2,x-16,y,attrib,SpriteROM,Palette+128);
	if((spr->flags & 0x30) == 0x30)
		drawtile_16x16_ddragon_spr(dest,pitch,tile+3,x-16,y-16,attrib,SpriteROM,Palette+128);
	}

//render top layer (working)
for(y=2;y<30;y++)				//32 tiles tall
	{
	for(x=0;x<32;x++)			//32 tiles wide
		{
		i = (x * 2) + (y * 32 * 2);
		tile = FGRAM[i + 1] | ((FGRAM[i + 0] & 7) << 8);
		attrib = (FGRAM[i+0] & 0xE0) >> 1;
		drawtile_8x8_ddragon(dest,pitch,tile,x * 8,y * 8,attrib,CharROM,Palette);
		}
	}
}

u8 CDoubleDragonMachine::ReadMainCPU(void *user,u32 addr)
{
CDoubleDragonMachine *M = (CDoubleDragonMachine*)user;

if(addr >= 0x8000)				//rom read
	return(M->MainROM[addr]);
if(addr >= 0x4000)				//banked rom
	return(M->ROMPtr[addr & 0x3FFF]);
if(addr < 0x3800)					//ram read
	return(M->RAM[addr]);
switch(addr)
	{
	case 0x3800:	return(M->InputData[0]);
	case 0x3801:	return(M->InputData[1]);
	case 0x3802:	return(M->InputData[2] | (M->Status & 0x18));
	case 0x3803:	return(0x00);//return(M->Dip1);
	case 0x3804:	return(0x00);//return(M->Dip2);
	}
message("unhandled read (main cpu, addr $%04X)\n",addr);
return(0);
}

void CDoubleDragonMachine::WriteMainCPU(void *user,u32 addr,u8 data)
{
CDoubleDragonMachine *M = (CDoubleDragonMachine*)user;

if(addr < 0x3800)					//ram write
	{
	M->RAM[addr] = data;
	if(addr >= 0x1000 && addr < 0x1400)
		M->WritePalette(addr,data);
	return;
	}
switch(addr)
	{
	case 0x3808:
		M->BankReg = data;
		M->ROMPtr = M->MainROM + 0x10000 + ((data & 0xE0) >> 5) * 0x4000;
		M->ScrollX &= 0xFF;
		M->ScrollY &= 0xFF;
		M->ScrollX |= (data & 1) << 8;
		M->ScrollY |= (data & 2) << 7;
		if(data & 0x10)
			M->Status &= ~0x10;
		else if((M->Status & 0x10) == 0)
			M->SubCPU->NMI();
		return;
	case 0x3809:	//scroll x low byte
		M->ScrollX &= 0x100;
		M->ScrollX |= data;
		return;
	case 0x380A:	//scroll y low byte
		M->ScrollY &= 0x100;
		M->ScrollY |= data;
		return;
	case 0x380B:	//nmi acknowledge
		return;
	case 0x380C:	//firq acknowledge
		return;
	case 0x380D:	//irq acknowledge
		return;
	case 0x380E:	//sound irq
		M->SoundLatch = data;
		M->SoundCPU->IRQ();
		return;
	case 0x380F:	//? (mame says mcu control)
		return;
	}
message("unhandled write (main cpu, addr $%04X = $%02X)\n",addr,data);
}

u8 CDoubleDragonMachine::ReadSubCPU(void *user,u32 addr)
{
CDoubleDragonMachine *M = (CDoubleDragonMachine*)user;

if(addr >= 0xC000)				//rom read
	return(M->SubROM[addr]);
if(addr < 0x1000)					//ram/internal register read
	{
	if(addr >= 0x20)
		return(M->SubRAM[addr]);
	//internal register read
	}
if((addr & 0xF000) == 0x8000)	//sprite ram read
	{
	//message("sub cpu: sprite ram read $%04X\n",addr);
	//MAME Double Dragon crash fix
	if(addr == 0x8049 && M->SubCPU->GetPC() == 0x6261 && MSPRRAM[addr & 0xFFF] == 0x1f)
		return(0x1);
	return(MSPRRAM[addr & 0xFFF]);
	}
message("unhandled read (sub cpu, addr $%04X)\n",addr);
return(0);
}

void CDoubleDragonMachine::WriteSubCPU(void *user,u32 addr,u8 data)
{
CDoubleDragonMachine *M = (CDoubleDragonMachine*)user;

if(addr < 0x1000)					//ram/internal register write
	{
	if(addr >= 0x20)
		{
		M->SubRAM[addr] = data;
		return;
		}
	if(addr == 0x17)
		{
		if(data & 3)
			{
			M->MainCPU->IRQ();
			return;
			}
		}
	}
if((addr & 0xF000) == 0x8000)	//sprite ram write
	{
	addr &= 0xFFF;
	if(addr == 0)
		M->Status |= 0x10;
	MSPRRAM[addr] = data;
	return;
	}
message("unhandled write (sub cpu, addr $%04X = $%02X)\n",addr,data);
}

u8 CDoubleDragonMachine::ReadSoundCPU(void *user,u32 addr)
{
CDoubleDragonMachine *M = (CDoubleDragonMachine*)user;
u32 s;

if(addr >= 0x8000)
	return(M->SoundROM[addr]);
if(addr < 0x1000)
	return(M->SoundRAM[addr]);
switch(addr)
	{
	case 0x1000:
		message("sound latch read\n");
		return(M->SoundLatch);
	case 0x1800:		//adpcm status
		return(M->IdleADPCM[0] | (M->IdleADPCM[1] << 1));
	case 0x2800:		//ym2151 status port 0
		s = (u32)YM2151ReadStatus(M->ym2151);
		return((u8)(s >> 8));
	case 0x2801:
		s = (u32)YM2151ReadStatus(M->ym2151);
		return((u8)(s >> 0));
	}
message("unhandled read (sound cpu, addr $%04X)\n",addr);
return(0);
}

void CDoubleDragonMachine::WriteSoundCPU(void *user,u32 addr,u8 data)
{
CDoubleDragonMachine *M = (CDoubleDragonMachine*)user;
u32 s;

if(addr < 0x1000)
	{
	M->SoundRAM[addr] = data;
	return;
	}
switch(addr)
	{
	case 0x2800:		//ym2151 register port 0
		M->YM2151Register = data;
		return;
	case 0x2801:		//ym2151 data port 0
		YM2151WriteReg(M->ym2151,M->YM2151Register,data);
		return;
	case 0x3800:
	case 0x3801:
	case 0x3802:
	case 0x3803:
	case 0x3804:
	case 0x3805:
	case 0x3806:
	case 0x3807:
		s = addr & 1;
		switch((addr >> 1) & 3)
			{
			case 0:
				M->IdleADPCM[s] = 0;
				MSM5205_reset_w(s,0);
				return;
			case 1:
				M->EndADPCM[s] = (data & 0x7F) * 0x200;
				return;
			case 2:
				M->PosADPCM[s] = (data & 0x7F) * 0x200;
				return;
			case 3:
				M->IdleADPCM[s] = 1;
				MSM5205_reset_w(s,1);
				return;
			}
		break;
	}
message("unhandled write (sound cpu, addr $%04X = $%02X)\n",addr,data);
}

void CDoubleDragonMachine::ProcessSound(void *user,void *data,int len)
{
int n;
s16 *dest = (s16*)data;
INT16 buffer0[1024],buffer1[1024],*buffers[] = {buffer0,buffer1};

if(user == 0)
	return;
YM2151UpdateOne(user,(SAMP**)buffers,len);
for(n=0;n<len;n++)
	dest[n] = (buffer0[n] + buffer1[n]) / 2;
MSM5205UpdateBuffer(buffers,len);
for(n=0;n<len;n++)
	dest[n] = (buffer0[n] + buffer1[n] + dest[n]) / 3;
}

void CDoubleDragonMachine::YM2151IRQ(void *user,int irq)
{
CDoubleDragonMachine *M = (CDoubleDragonMachine*)user;

if(irq && M)
	{
	message("sound firq\n");
	M->SoundCPU->FIRQ();
	}
}
